var libxml;

function libxml(path){
    libxml = require(path);
    return exports;
}

function toString(){
    return this['@value'];
}

function parse(/**String*/type)/**Function*/{
    return function(/**Function*/callback,/**String*/string)/**libxml.SaxPushParser*/{
        var current = {}, parents = [],
        handler = function(cb){
            cb.onEndDocument(function(){
                callback(current);
            });
            cb.onStartElementNS(function(el, attrs){
                if (el in current) {
                    if (!('length' in current[el])) {
                        current[el] = [current[el]];
                    }
                    current[el].push({});
                    parents.unshift(current);
                    current = current[el][current[el].length - 1];
                } else {
                    current[el] = {};
                    parents.unshift(current);
                    current = current[el];
                }
                if (!attrs.length) return;
                current['@attrs'] = {};
                for (var i = attrs.length - 1; i > -1; --i) {
                    current['@attrs'][attrs[i][0]] = attrs[i][3];
                }
            });
            cb.onEndElementNS(function(el){
                current = parents.shift();
                if (parents.length && 'length' in parents[parents.length - 1]) return;
                if (!('@value' in current[el])) return;
                if ('@attrs' in current[el]) {
                    current[el].toString = toString;
                } else {
                    current[el] = current[el]['@value'];
                }
            });
            cb.onCharacters(function(chars){
                current['@value'] = chars;
            });
            cb.onCdata(function(cdata){
                current['@value'] = cdata;
            });
        };
        if ('chunk' == type) {
            return new libxml.SaxPushParser(handler);
        } else if ('file' == type) {
            new libxml.SaxParser(handler).parseFile(string);
        } else if ('string' == type) {
            new libxml.SaxParser(handler).parseString(string);
        }
    };
}

/**
 *
 * @name xml2json
 * @namespace
 */

/**
 * Set libxmljs location.
 *
 * @name xml2json.libxml
 * @function
 * @param {String} path
 * @returns {xml2json} Self reference
 */
exports.libxml = libxml;

/**
 * Convert xml string to json.
 *
 * @name xml2json.string
 * @function
 * @param {Function} callback
 * @param {String} string
 */
exports.string = parse('string');

/**
 * Return instance of push parser.
 *
 * @name xml2json.chunk
 * @function
 * @type {libxml.PushSaxParser}
 * @param {Function} callback
 * @returns {libxml.PushSaxParser} Instance of push parser
 */
exports.chunk = parse('chunk');

/**
 * Convert xml from file to json.
 *
 * @name xml2json.file
 * @function
 * @param {Function} callback
 * @param {String} filename
 */
exports.file = parse('file');
